package com.net.protocol.message;

import com.net.protocol.exception.IllegalMessageException;
import com.net.protocol.message.body.receiver.Common_ACK;
import com.net.protocol.message.body.receiver.Search_ACK;
import com.net.protocol.message.body.sender.LoginASK;
import com.net.protocol.message.body.sender.SearchTimeASK;
import com.net.protocol.util.BitUtils;
import com.net.protocol.util.Logger;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 协议消息
 * --------------------------------------------
 * | 标识位 | 消息头 | 消息体 | 校验码 | 标识位 |
 * --------------------------------------------
 */
public class Message<T> {

    private static final String TAG = "{Message}";

    /**
     * 终端请求
     */
    public static final int MESSAGE_ID_CLIENT_LOGIN = 0x8007; //终端登录
    public static final int MESSAGE_ID_CLIENT_SEARCH = 0x8001; //终端搜索

    /**
     * 终端应答
     */
    public static final int MESSAGE_ID_SERVER_ACK_COMMON = 0x0003; //终端通用应答
    public static final int MESSAGE_ID_SERVER_ACK_SEARCH = 0x0001; //终端搜索应答


    public static final Map<Class<? extends Body.Sender>, Integer> SENDER_ID_MAPPER = new HashMap<>(); //发送消息ID映射表
    public static final Map<Integer, Class<? extends Body.Receiver>> RECEIVER_ID_MAPPER = new HashMap<>(); //接收消息ID映射表

    static {
        /**
         * 终端请求
         */
        SENDER_ID_MAPPER.put(LoginASK.class, MESSAGE_ID_CLIENT_LOGIN);
        //将搜索请求放入集合
        SENDER_ID_MAPPER.put(SearchTimeASK.class, MESSAGE_ID_CLIENT_SEARCH);

        /**
         * 终端应答
         */
        RECEIVER_ID_MAPPER.put(MESSAGE_ID_SERVER_ACK_COMMON,Common_ACK.class);
        RECEIVER_ID_MAPPER.put(MESSAGE_ID_SERVER_ACK_SEARCH,Search_ACK.class);

    }

    /**
     * 解析结果
     */
    public static final int MESSAGE_STATUS_OK = 0; // 读取正常
    public static final int MESSAGE_STATUS_LACK = 1; // 数据缺失

    /**
     * 标识位
     */
    public static final int SYMBOL_BEGIN = 0xEB90; //起始标识

    /**
     * 起始字节	字段	数据类型	说明
     *2+0	设备ID	BCD[6]	对应设备ID，可根据硬件生成，预留定义
     *2+6	消息流水号	WORD	按发送顺序从0开始循环累加
     *2+8	消息ID	WORD	消息的命令码
     *2+10	消息体长度	WORD	消息体长度
     * ------------------------------------------------------------------
     */
    public static final class Header {
        private String id; //设备ID
        private int seqId; //消息流水号

        public int getMsgId() {
            return MsgId;
        }

        public void setMsgId(int msgId) {
            MsgId = msgId;
        }

        private int MsgId; //消息Id
        private int bodySize; //消息体长度

        public Header() {
        }

        public Header(String id, int bodySize) {
            this.id = id;
            this.bodySize = bodySize;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public int getBodySize() {
            return bodySize;
        }

        public void setBodySize(int bodySize) {
            this.bodySize = bodySize;
        }

        public long getSeqId() {
            return seqId;
        }

        public void setSeqId(int seqId) {
            this.seqId = seqId;
        }

        @Override
        public String toString() {
            return "Header{id=" + id + ", bodySize=" + bodySize +
                    ", seqId=" + seqId +'}';
        }

        public int getSize() {
            return 12;
        }

        /**
         * 打包消息头信息
         *
         * @param buf    发送缓冲
         * @param offset 偏移量
         */
        public byte[] pack(byte[] buf, int offset) {
            BitUtils.setBCD(buf, offset, id, 6); //设备ID号
            BitUtils.setInt16(buf, offset+6, seqId); //消息ID
            BitUtils.setInt16(buf, offset + 8, MsgId); //消息号
            BitUtils.setInt16(buf, offset + 10, bodySize); //消息体长度
            return buf;
        }

        /**
         * 解包
         *
         * @param buf    接收缓冲
         * @param offset 偏移量
         */
        public static Header unPack(byte[] buf, int offset) {
            Header header = new Header();
            header.setId(String.valueOf(BitUtils.readBCD(buf, offset,6))); //消息ID
            header.seqId = BitUtils.readInt16(buf, offset + 6);
            header.MsgId = BitUtils.readInt16(buf,offset+8);
            header.bodySize = BitUtils.readInt16(buf,offset+10);
            return header;
        }
    }

    /**
     * 消息体
     */
    public interface Body {

        /**
         * 终端发送的消息
         */
        public interface Sender extends Body {

            /**
             * 终端请求
             */
            public interface ASK extends Sender {
            }

            /**
             * 终端应答
             */
            public interface ACK extends Sender {
            }

            /**
             * 将消息体转换为字节数组
             */
            public byte[] pack(byte[] buf, int offset) throws UnsupportedEncodingException;

            /**
             * 获取消息体长度
             */
            public int getSize() throws UnsupportedEncodingException;
        }

        /**
         * 客户端接收的消息(平台发送的消息)
         */
        public interface Receiver extends Body {

            /**
             * 平台请求
             */
            public interface ASK extends Receiver {
            }

            /**
             * 平台应答
             */
            public interface ACK extends Receiver {
            }

            /**
             * 解析
             */
            public void unpack(byte[] buf, int offset, int length) throws UnsupportedEncodingException;
        }
    }

    private int status; //解析结果
    private Header header; //消息头
    private T body; //消息体
    private byte checkCode; //校验码指从消息头开始，同后一字节异或，直到校验码前一个字节，占用一个字节

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Header getHeader() {
        return header;
    }

    public void setHeader(Header header) {
        this.header = header;
    }

    public T getBody() {
        return body;
    }

    public void setBody(T body) {
        this.body = body;
    }

    public byte getCheckCode() {
        return checkCode;
    }

    public void setCheckCode(byte checkCode) {
        this.checkCode = checkCode;
    }

    @Override
    public String toString() {
        return "Message{status=" + status + ", header=" + header + ", body=" + body + ", checkCode=" + checkCode + '}';
    }

    /**
     * 生成校验码
     *
     * @param buf    缓冲
     * @param offset 消息头偏移量
     * @param length 数据长度(消息头+消息体)
     */
    private static byte genCheckCode(byte[] buf, int offset, int length) {
        byte code = buf[offset];
        for (int i = 1; i < length; i++) {
            code ^= buf[offset + i];
        }
        return code;
    }

    /**
     * 消息包的长度
     */
    public int getSize() {
        return header.getSize() + header.getBodySize() + 3;
    }

    /**
     * 打包消息
     *
     * @param sendBuf 发送缓冲
     * @return 打包并转义后数据的长度
     * @throws IllegalMessageException
     */
    public int pack(byte[] sendBuf) throws IllegalMessageException {
        if (!(body instanceof Body.Sender)) { //检查消息体是否合法
            Logger.e(TAG, "请求消息体必须实现Body.Sender接口");
            throw new IllegalMessageException("请求消息体必须实现Body.Sender接口");
        }
        BitUtils.setInt16(sendBuf,0,SYMBOL_BEGIN);//开始符
        header.pack(sendBuf, 2); //设置消息头
        Body.Sender sendBody = (Body.Sender) body;
        try {
            sendBody.pack(sendBuf, 2 + header.getSize()); //设置消息体
            sendBuf[2 + header.getSize() + sendBody.getSize()] = genCheckCode(sendBuf, 2, header.getSize() + sendBody.getSize()); //校验码
            return 3 + header.getSize() + sendBody.getSize();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            Logger.e(TAG, "消息编码格式不合法");
            throw new IllegalMessageException("消息编码格式不合法");
        }
    }

    /**
     * 解包(接收到的数据)
     *
     * @param receiveBuf 接收缓冲
     * @param length     接收到的数据长度
     */
    public static Message unpack(byte[] receiveBuf, int offset, int length) throws IllegalMessageException {
        //Logger.i(TAG, "解包消息：" + Hex.encodeHexStr(Arrays.copyOfRange(receiveBuf, offset, offset + length))); //转义前数据
        int begin = BitUtils.readInt16(receiveBuf, 0);
        if (begin != SYMBOL_BEGIN) { //起始标识
            Logger.e(TAG, "消息起始位不正确");
            throw new IllegalMessageException("消息起始位不正确");
        }
        Message msg = new Message();
        if (length < 15) {
            msg.setStatus(MESSAGE_STATUS_LACK); //消息长度不足
            return msg;
        }
        Header header = Header.unPack(receiveBuf, offset + 2); //解析消息头
        msg.setHeader(header);
        if (RECEIVER_ID_MAPPER.get(header.getMsgId()) == null) {
            Logger.e(TAG, "接收到的消息ID" + header.getId() + "不合法");
            throw new IllegalMessageException("接收到的消息ID" + header.getId() + "不合法");
        }
        if (header.getBodySize() >= Math.pow(2, 10)) {
            Logger.e(TAG, "消息体长度不正确");
            throw new IllegalMessageException("消息体长度不正确");
        }
        if (3 + header.getSize() + header.getBodySize() > length) {
            msg.setStatus(MESSAGE_STATUS_LACK); //消息长度不足
            return msg;
        }

        Body.Receiver receiveBody = null;
        try {
            receiveBody = RECEIVER_ID_MAPPER.get(header.getId()).newInstance(); //获取消息体类型
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        if (receiveBody == null) {
            Logger.e(TAG, "接收到的消息ID" + header.getId() + "不合法");
            throw new IllegalMessageException("接收到的消息ID" + header.getId() + "不合法");
        }

        try {
            receiveBody.unpack(receiveBuf, offset + 2 + header.getSize(), header.getBodySize()); //解析消息体
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            Logger.e(TAG, "消息编码格式不合法");
            throw new IllegalMessageException("消息编码格式不合法");
        }
        msg.setBody(receiveBody); //设置消息体

        /**
         * 检查校验码
         */
        int checkCode = receiveBuf[offset + 2 + header.getSize() + header.getBodySize()]; //校验码
        if (!(checkCode == genCheckCode(receiveBuf, offset + 2, header.getSize() + header.getBodySize()))) {
            Logger.e(TAG, "消息校验码不正确");
            throw new IllegalMessageException("消息校验码不正确");
        }
        msg.setStatus(MESSAGE_STATUS_OK); //解析成功
        return msg;
    }

}
